package com.yuliang.utils.DistributedLock;

import com.yuliang.utils.util.RedisUtils;
import com.yuliang.utils.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Huangkai
 */

@Slf4j
public class DistributedLock implements Lock {
    /**
     * 标识 id
     */
    private final String uuid = StringUtils.getUUID();

    /**
     * 资源名称
     */
    private String resourceName;

    private List<String> keys = new ArrayList<>(1);


    /**
     * redis 客户端
     */
    private final RedisTemplate redisTemplate;

    public DistributedLock(String resourceName, RedisTemplate redisTemplate) {
        this.resourceName = resourceName;
        this.redisTemplate = redisTemplate;
        keys.add(resourceName);
    }

    private String getRequestId(long threadId) {
        return uuid + ":" + threadId;
    }

    @Override
    public boolean isLock(String resourceName) {
        return false;
    }

    @Override
    public boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) {
        long time = unit.toMillis(waitTime);
        long current = System.currentTimeMillis();
        long threadId = Thread.currentThread().getId();
        // 获取锁
        Boolean isAcquire = tryAcquire(leaseTime, unit, threadId);
        // lock acquired
        if (Boolean.TRUE.equals(isAcquire)) {
            return true;
        }

        time -= System.currentTimeMillis() - current;
        // 等待时间用完，获取锁失败
        if (time <= 0) {
            return false;
        }
        // 自旋获取锁
        while (true) {
            long currentTime = System.currentTimeMillis();
            isAcquire = tryAcquire(leaseTime, unit, threadId);
            // lock acquired
            if (Boolean.TRUE.equals(isAcquire)) {
                return true;
            }
            time -= System.currentTimeMillis() - currentTime;
            if (time <= 0) {
                return false;
            }
        }
    }

    @Override
    public void lock(long leaseTime, TimeUnit unit) {
        long threadId = Thread.currentThread().getId();
        Boolean acquired;
        do {
            acquired = tryAcquire(leaseTime, unit, threadId);
        } while (Boolean.TRUE.equals(acquired));
    }
    @Override
    public void unlock() {
        long threadId = Thread.currentThread().getId();
        String value = getRequestId(threadId);
        RedisUtils.releaseLock(resourceName, value);
    }

    /**
     * 尝试获取锁
     * @param leaseTime
     * @param unit
     * @param threadId
     * @return
     */
    private Boolean tryAcquire(long leaseTime, TimeUnit unit, long threadId) {
        String value = getRequestId(threadId);
        Boolean flag = RedisUtils.setIfAbsent(resourceName, value, leaseTime, unit);
        return flag;
    }
}
